// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

/**********************************************************
 * This script provides syntax highlighting support for
 * the N-Triples format.
 * N-Triples format specification:
 *     https://www.w3.org/TR/n-triples/
 ***********************************************************/

/*
    The following expression defines the defined ASF grammar transitions.

    pre_subject ->
        {
        ( writing_subject_uri | writing_bnode_uri )
            -> pre_predicate
                -> writing_predicate_uri
                    -> pre_object
                        -> writing_object_uri | writing_object_bnode |
                          (
                            writing_object_literal
                                -> writing_literal_lang | writing_literal_type
                          )
                            -> post_object
                                -> BEGIN
         } otherwise {
             -> ERROR
         }
*/

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.defineMode('ntriples', function() {
    var Location = {
      PRE_SUBJECT: 0,
      WRITING_SUB_URI: 1,
      WRITING_BNODE_URI: 2,
      PRE_PRED: 3,
      WRITING_PRED_URI: 4,
      PRE_OBJ: 5,
      WRITING_OBJ_URI: 6,
      WRITING_OBJ_BNODE: 7,
      WRITING_OBJ_LITERAL: 8,
      WRITING_LIT_LANG: 9,
      WRITING_LIT_TYPE: 10,
      POST_OBJ: 11,
      ERROR: 12,
    };
    function transitState(currState, c) {
      var currLocation = currState.location;
      var ret;

      // Opening.
      if (currLocation == Location.PRE_SUBJECT && c == '<')
        ret = Location.WRITING_SUB_URI;
      else if (currLocation == Location.PRE_SUBJECT && c == '_')
        ret = Location.WRITING_BNODE_URI;
      else if (currLocation == Location.PRE_PRED && c == '<')
        ret = Location.WRITING_PRED_URI;
      else if (currLocation == Location.PRE_OBJ && c == '<')
        ret = Location.WRITING_OBJ_URI;
      else if (currLocation == Location.PRE_OBJ && c == '_')
        ret = Location.WRITING_OBJ_BNODE;
      else if (currLocation == Location.PRE_OBJ && c == '"')
        ret = Location.WRITING_OBJ_LITERAL;
      // Closing.
      else if (currLocation == Location.WRITING_SUB_URI && c == '>')
        ret = Location.PRE_PRED;
      else if (currLocation == Location.WRITING_BNODE_URI && c == ' ')
        ret = Location.PRE_PRED;
      else if (currLocation == Location.WRITING_PRED_URI && c == '>')
        ret = Location.PRE_OBJ;
      else if (currLocation == Location.WRITING_OBJ_URI && c == '>')
        ret = Location.POST_OBJ;
      else if (currLocation == Location.WRITING_OBJ_BNODE && c == ' ')
        ret = Location.POST_OBJ;
      else if (currLocation == Location.WRITING_OBJ_LITERAL && c == '"')
        ret = Location.POST_OBJ;
      else if (currLocation == Location.WRITING_LIT_LANG && c == ' ')
        ret = Location.POST_OBJ;
      else if (currLocation == Location.WRITING_LIT_TYPE && c == '>')
        ret = Location.POST_OBJ;
      // Closing typed and language literal.
      else if (currLocation == Location.WRITING_OBJ_LITERAL && c == '@')
        ret = Location.WRITING_LIT_LANG;
      else if (currLocation == Location.WRITING_OBJ_LITERAL && c == '^')
        ret = Location.WRITING_LIT_TYPE;
      // Spaces.
      else if (
        c == ' ' &&
        (currLocation == Location.PRE_SUBJECT ||
          currLocation == Location.PRE_PRED ||
          currLocation == Location.PRE_OBJ ||
          currLocation == Location.POST_OBJ)
      )
        ret = currLocation;
      // Reset.
      else if (currLocation == Location.POST_OBJ && c == '.')
        ret = Location.PRE_SUBJECT;
      // Error
      else ret = Location.ERROR;

      currState.location = ret;
    }

    return {
      startState: function() {
        return {
          location: Location.PRE_SUBJECT,
          uris: [],
          anchors: [],
          bnodes: [],
          langs: [],
          types: [],
        };
      },
      token: function(stream, state) {
        var ch = stream.next();
        if (ch == '<') {
          transitState(state, ch);
          var parsedURI = '';
          stream.eatWhile(function(c) {
            if (c != '#' && c != '>') {
              parsedURI += c;
              return true;
            }
            return false;
          });
          state.uris.push(parsedURI);
          if (stream.match('#', false)) return 'variable';
          stream.next();
          transitState(state, '>');
          return 'variable';
        }
        if (ch == '#') {
          var parsedAnchor = '';
          stream.eatWhile(function(c) {
            if (c != '>' && c != ' ') {
              parsedAnchor += c;
              return true;
            }
            return false;
          });
          state.anchors.push(parsedAnchor);
          return 'variable-2';
        }
        if (ch == '>') {
          transitState(state, '>');
          return 'variable';
        }
        if (ch == '_') {
          transitState(state, ch);
          var parsedBNode = '';
          stream.eatWhile(function(c) {
            if (c != ' ') {
              parsedBNode += c;
              return true;
            }
            return false;
          });
          state.bnodes.push(parsedBNode);
          stream.next();
          transitState(state, ' ');
          return 'builtin';
        }
        if (ch == '"') {
          transitState(state, ch);
          stream.eatWhile(function(c) {
            return c != '"';
          });
          stream.next();
          if (stream.peek() != '@' && stream.peek() != '^') {
            transitState(state, '"');
          }
          return 'string';
        }
        if (ch == '@') {
          transitState(state, '@');
          var parsedLang = '';
          stream.eatWhile(function(c) {
            if (c != ' ') {
              parsedLang += c;
              return true;
            }
            return false;
          });
          state.langs.push(parsedLang);
          stream.next();
          transitState(state, ' ');
          return 'string-2';
        }
        if (ch == '^') {
          stream.next();
          transitState(state, '^');
          var parsedType = '';
          stream.eatWhile(function(c) {
            if (c != '>') {
              parsedType += c;
              return true;
            }
            return false;
          });
          state.types.push(parsedType);
          stream.next();
          transitState(state, '>');
          return 'variable';
        }
        if (ch == ' ') {
          transitState(state, ch);
        }
        if (ch == '.') {
          transitState(state, ch);
        }
      },
    };
  });

  // define the registered Media Type for n-triples:
  // https://www.w3.org/TR/n-triples/#n-triples-mediatype
  CodeMirror.defineMIME('application/n-triples', 'ntriples');

  // N-Quads is based on the N-Triples format (so same highlighting works)
  // https://www.w3.org/TR/n-quads/
  CodeMirror.defineMIME('application/n-quads', 'ntriples');

  // previously used, though technically incorrect media type for n-triples
  CodeMirror.defineMIME('text/n-triples', 'ntriples');
});
